/**
 * 
 */
package zeroj.resource;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import zeroj.helper.ClassLoaderUtil;

/**
 * @author LYL(lylsir at gmail dot com)
 *
 */
public final class ResourceInjector {

	private static ResourceContainer container = null;
	
	public static void initResources(final String packageNames) {
		
		container = new ResourceContainer(packageNames);
		container.resourceInit();
	}
	
	public static void inject(Object object) {
		
		container.injectResource(object);
	}
	public static void destroy() {
		container.resourceDestroy();
	}
}

class ResourceContainer {

	private static final Log log = LogFactory.getLog(ResourceContainer.class);
	private Map<String, Object> resources = new HashMap<String, Object>();
	
	ResourceContainer(String packageNames) {
		init(packageNames);
	}
	private void init(String packageNames) {
		
		String[] packages = packageNames.split(",");
		List<Class<?>> classes = new ArrayList<Class<?>>();
		for(String packageName : packages) {
			
			classes.addAll(ClassLoaderUtil.loadPackageClasses(packageName.trim(), true));
		}
		for(Class<?> clz : classes) {
			
			if(! (clz.isInterface() && clz.isEnum() && clz.isAnnotation() && clz.isEnum()) ) {
				
				Resource resourceAnnotation = clz.getAnnotation(Resource.class);
				if(resourceAnnotation == null)
					continue;
				if(resources.containsKey(resourceAnnotation.name())) {
					log.error("Dupicated resource " + resourceAnnotation.name());
					throw new RuntimeException("Duplicated resources for " + resourceAnnotation.name());
				}
				try {
					resources.put(resourceAnnotation.name(), clz.newInstance());
				} catch (Exception e) {
					log.info("Cannot instance " + clz.getName());
				}
			}
		}
		injectResources();
	}
	private void injectResources() {
		
		for(String name : resources.keySet()) {
			
			Object resource = resources.get(name);
			injectResource(resource);
		}
	}
	void resourceInit() {
		
		for(String name : resources.keySet()) {
			
			Object resource = resources.get(name);
			if(resource instanceof Initializable) {
				
				((Initializable)resource).init();
			}
		}
	}
	void resourceDestroy() {
		
		for(String name : resources.keySet()) {
			
			Object resource = resources.get(name);
			if(resource instanceof Destroyable) {
				((Destroyable)resource).destroy();
			}
		}
	}
	public void injectResource(Object object) {
		
		Class<?> clz = object.getClass();
		Field[] fields = clz.getDeclaredFields();
		for(Field field : fields) {
			Inject inject = field.getAnnotation(Inject.class);
			if(inject != null) {
				Object resource = resources.get(inject.value());
				if(resource == null) {
					log.error("Cannot find resource " + inject.value() + " to inject");
					throw new RuntimeException("Cannot find resource " + inject.value() + " to inject");
				}
				if(! field.getType().isAssignableFrom(resource.getClass())) {
					log.error("Cannot inject resource " + inject.value() + " to " + field.toString());
					throw new RuntimeException("Cannot inject resource " + inject.value() + " to " + field.toString());
				}
				field.setAccessible(true);
				try {
					field.set(object, resource);
				} catch (IllegalAccessException iae) {
					log.error("could not inject server for attribute " + field.getName(), iae);
					throw new RuntimeException("could not inject server for attribute " + field.getName() + ". Reason " + iae.toString());
				}
			}
		}
	}
}

